﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Linq;

namespace Xceed.Silverlight.ListBox
{
  internal class ScrollingUpTransitionAnimation : TransitionAnimation
  {

    #region Constructor
    public ScrollingUpTransitionAnimation( ListBoxAnimationSettings animationSettings )
      : base( animationSettings.GetThumbScrollingDuration() )
    {
      PowerEase opacityEasing = new PowerEase();
      opacityEasing.EasingMode = EasingMode.EaseOut;
      opacityEasing.Power = 2;

      PowerEase itemsRemovedEasing = new PowerEase();
      itemsRemovedEasing.EasingMode = EasingMode.EaseOut;
      itemsRemovedEasing.Power = 3;

      BackEase otherItemsEasing = new BackEase();
      otherItemsEasing.EasingMode = EasingMode.EaseIn;
      otherItemsEasing.Amplitude = 0.5;

      m_opacityEasing = opacityEasing;
      m_animatedItemsMovementEasing = itemsRemovedEasing;
      this.Orientation = Orientation.Vertical;
    }
    #endregion

    #region Orientation Property
    internal Orientation Orientation
    {
      get;
      set;
    }
    #endregion

    #region Internal Methods
    internal override LayoutChangeInfo CreateLayoutChangeInfo()
    {
      return new LayoutChangeInfo();
    }

    internal override void AddContainer( ListBoxContainer container )
    {
      if( !this.IsRunning )
        return;

      LayoutChangeInfo layoutChange = this.CreateLayoutChangeInfo();

      if( this.PrepareContainer( container, layoutChange ) )
      {
        //Update the element with start time, ensure that as soon as "start" completes, state if correct for all items.
        if( !this.UpdateContainer( 0d, container ) )
        {
          //If both preparation let pass the item and update did not filter it out, mark the item as managed by TransitionAnimation
          this.HandledItems.Add( container );
        }
      }
    }

    internal override void DetermineLayoutChangesCore( Dictionary<ListBoxContainer, LayoutChangeInfo> affectedElement )
    {

      IEnumerable<LayoutChangeInfo> changedElements = from kvp in affectedElement
                                                      let layoutChangeInfo = kvp.Value
                                                      let container = kvp.Key
                                                      where layoutChangeInfo.ChangeType == ChangeType.ElementChanged
                                                      select layoutChangeInfo;

      // If a changed element is part of the tarnsiton 
      if( changedElements.Count() > 0 )
      {
        // The scrollin to apply is the offset change of the changed element
        if( this.Orientation == Orientation.Vertical )
        {
          m_scrollingOffsetInPixels.X = double.NaN;
          m_scrollingOffsetInPixels.Y = changedElements.First().YChange;
        }
        else
        {
          m_scrollingOffsetInPixels.X = changedElements.First().XChange;
          m_scrollingOffsetInPixels.Y = double.NaN;
        }
      }
      else
      {

        IEnumerable<ListBoxContainer> removedElements = from kvp in affectedElement
                                                        let layoutChangeInfo = kvp.Value
                                                        let container = kvp.Key
                                                        where layoutChangeInfo.ChangeType == ChangeType.ElementRemoved
                                                        select container;

        IEnumerable<ListBoxContainer> addedElements = from kvp in affectedElement
                                                      let layoutChangeInfo = kvp.Value
                                                      let container = kvp.Key
                                                      where layoutChangeInfo.ChangeType == ChangeType.ElementAdded
                                                      select container;

        Rect addedOperationRect = this.ComputeOperationsRect( addedElements );
        Rect removedOperationRect = this.ComputeOperationsRect( removedElements );

        // Compute the scrolling offset to apply to all containers in the layout
        // to move from the original layout to the new layout
        if( this.Orientation == Orientation.Vertical )
        {
          m_scrollingOffsetInPixels.X = double.NaN;
          m_scrollingOffsetInPixels.Y = removedOperationRect.Y - addedOperationRect.Height - addedOperationRect.Y;
        }
        else
        {
          m_scrollingOffsetInPixels.X = removedOperationRect.X - addedOperationRect.Width - addedOperationRect.X;
          m_scrollingOffsetInPixels.Y = double.NaN;
        }
      }
    }

    internal override bool PrepareContainerCore( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {

      if( changeInfo == null )
        throw new ListBoxInternalException();

      if (( changeInfo.ChangeType == ChangeType.ElementAdded ) ||
          ( changeInfo.ChangeType == ChangeType.ElementChanged ) )
      {

        PositionAnimation positionAnimation = new PositionAnimation();

        ( ( IAnimation )positionAnimation ).ParentTransition = this;
        ( ( IAnimation )positionAnimation ).EasingFunction = m_animatedItemsMovementEasing;

        positionAnimation.XChange = m_scrollingOffsetInPixels.X;
        positionAnimation.YChange = m_scrollingOffsetInPixels.Y;

        container.AddAnimation( positionAnimation );

        return true;
      }
      else
      {
        // Changed Element == ElementRemoved

        PositionAnimation positionAnimation = new PositionAnimation();
        ( ( IAnimation )positionAnimation ).EasingFunction = m_animatedItemsMovementEasing;
        ( ( IAnimation )positionAnimation ).ParentTransition = this;
        ( ( IAnimation )positionAnimation ).AnimationDirection = AnimationDirection.FromReferencePosition;

        SizeAnimation sizeAnimation = new SizeAnimation();
        ( ( IAnimation )sizeAnimation ).EasingFunction = m_animatedItemsMovementEasing;
        ( ( IAnimation )sizeAnimation ).ParentTransition = this;

        positionAnimation.XChange = -m_scrollingOffsetInPixels.X;
        positionAnimation.YChange = -m_scrollingOffsetInPixels.Y;

        sizeAnimation.MaximumWidthChange = -m_scrollingOffsetInPixels.X;
        sizeAnimation.MaximumHeightChange = -m_scrollingOffsetInPixels.Y;
        sizeAnimation.WidthChange = double.NaN;
        sizeAnimation.HeightChange = double.NaN;

        container.AddAnimation( positionAnimation );
        container.AddAnimation( sizeAnimation );

        return true;
      }
    }

    internal override bool UpdateContainerCore( double normalizedElapsedTime, ListBoxContainer container )
    {
      if( container == null )
        throw new ListBoxInternalException("container cannot be null.");

      container.UpdateAnimations( normalizedElapsedTime );

      //There is no custom animations in this transition, only those handled by the EasingFunctions.
      //Since I have no custom timeline, I do not have to decide when its "finished". Returning false
      //will just make it so that the animation will proceed for every frame of every item.
      return false;
    }

    internal override void CleanContainerCore( ListBoxContainer container )
    {
      if( container == null )
        throw new ListBoxInternalException();
    }
    #endregion

    #region Private Methods
    private Rect ComputeOperationsRect( IEnumerable<ListBoxContainer> elements )
    {
      bool operationOccured = false;
      double lowestOperationX = double.MaxValue;
      double lowestOperationY = double.MaxValue;
      double highestOperationX = double.MinValue;
      double highestOperationY = double.MinValue;

      foreach( ListBoxContainer listBoxContainer in elements )
      {
        Point containerPosition = listBoxContainer.AbsoluteOffset;

        double elementHeight = listBoxContainer.AbsoluteElementHeight;
        double elementWidth = listBoxContainer.AbsoluteElementWidth;

        if( containerPosition.X < lowestOperationX )
        {
          lowestOperationX = containerPosition.X;
        }

        if( containerPosition.Y < lowestOperationY )
        {
          lowestOperationY = containerPosition.Y;
        }

        if( ( containerPosition.X + elementWidth ) > highestOperationX )
        {
          highestOperationX = containerPosition.X + elementWidth;
        }

        if( ( containerPosition.Y + elementHeight ) > highestOperationY )
        {
          highestOperationY = containerPosition.Y + elementHeight;
        }

        operationOccured = true;
      }

      if( operationOccured )
      {
        return new Rect( lowestOperationX, lowestOperationY, highestOperationX - lowestOperationX, highestOperationY - lowestOperationY );
      }
      else
      {
        return Rect.Empty;
      }
    }

    #endregion

    #region Private Members
    private IEasingFunction m_opacityEasing;
    private IEasingFunction m_animatedItemsMovementEasing;
    private Point m_scrollingOffsetInPixels = new Point( 0.0d, 0.0d );
    #endregion

  }
}
